Skip to content

Comments

Allow FetchSubPhaseProcessors to report their required stored fields …#1

Open
MitchLewis930 wants to merge 1 commit intopr_011_beforefrom
pr_011_after
Open

Allow FetchSubPhaseProcessors to report their required stored fields …#1
MitchLewis930 wants to merge 1 commit intopr_011_beforefrom
pr_011_after

Conversation

@MitchLewis930
Copy link

@MitchLewis930 MitchLewis930 commented Jan 29, 2026

PR_011

Summary by CodeRabbit

  • New Features

    • Added StoredFieldsPhase to fetch profiling output, providing granular timing breakdowns for stored fields operations during search queries.
  • Refactors

    • Internal improvements to fetch sub-phase architecture and stored fields handling for better performance metrics collection.

✏️ Tip: You can customize this high-level summary in your review settings.

…lastic#91269)

Loading of stored fields is currently handled directly in FetchPhase, with
some fairly complex logic examining various bits of the FetchContext to work
out what fields need to be loaded. This is further complicated by synthetic
source, which may have its own stored field requirements.

This commit tries to separate out these concerns a little by adding a new
StoredFieldsSpec record that holds information about which stored fields
need to be loaded. Each FetchSubPhaseProcessor can now report a
StoredFieldsSpec detailing what its requirements are, and these specs can
be merged together, along with requirements from a SourceLoader, to
determine up-front what fields should be loaded by the StoredFieldLoader.
The stored fields themselves are added into the SearchHit by a new
StoredFieldsPhase, which handles alias resolution and value post-
processing. The logic to determine when source should be loaded and
when not, based on the presence of script fields or stored fields, is
moved into FetchContext, which highlights some inconsistencies that
can be fixed in follow-up commits.
@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

This pull request refactors Elasticsearch's fetch and stored fields handling to improve modularity and clarity. Key changes include introducing a new StoredFieldsSpec abstraction and StoredFieldsPhase fetch sub-phase to explicitly manage stored field loading, simplifying the SearchHit constructor to accept only id and docId with a separate addDocumentFields method, updating all FetchSubPhaseProcessor implementations to provide a storedFieldsSpec() method, and refactoring core fetch logic in FetchPhase and FetchContext to use this new specification-based approach.

Changes

Cohort / File(s) Summary
Core Stored Fields Abstraction
server/src/main/java/org/elasticsearch/search/fetch/StoredFieldsSpec.java, server/src/main/java/org/elasticsearch/search/fetch/subphase/StoredFieldsPhase.java
Introduces new StoredFieldsSpec record and StoredFieldsPhase fetch sub-phase for explicit specification and loading of stored fields during fetch operations.
FetchSubPhaseProcessor Interface Extension
server/src/main/java/org/elasticsearch/search/fetch/FetchSubPhaseProcessor.java, server/src/main/java/org/elasticsearch/search/fetch/subphase/*.java (15+ files)
Adds required storedFieldsSpec() method to FetchSubPhaseProcessor interface; all fetch sub-phase implementations updated to provide their field requirements.
Core Fetch Infrastructure Refactoring
server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java, server/src/main/java/org/elasticsearch/search/fetch/FetchContext.java, server/src/main/java/org/elasticsearch/search/fetch/FetchSubPhase.java
Rewires fetch phase to use StoredFieldsSpec-based approach; updates FetchContext with stored fields context; modifies FetchSubPhase.HitContext constructor to accept loadedFields map; refactors field loading logic.
SearchHit Constructor Simplification
server/src/main/java/org/elasticsearch/search/SearchHit.java
Simplifies constructor from 4-argument form to 2-argument form (docId, id); adds pre-initialized documentFields/metaFields maps; introduces addDocumentFields() method for post-construction field attachment.
Field Mapper Refactoring
server/src/main/java/org/elasticsearch/index/mapper/IgnoredFieldMapper.java, server/src/main/java/org/elasticsearch/index/mapper/LegacyTypeFieldMapper.java, server/src/main/java/org/elasticsearch/index/mapper/RoutingFieldMapper.java
Introduces public static FIELD_TYPE fields in each mapper class; removes inner-class INSTANCE references; updates constructors accordingly.
SearchHit Construction Updates - rank-eval Tests
modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/*.java (6 files)
Updates all test SearchHit instantiations to use simplified 2-argument constructor instead of 4-argument form with empty maps.
SearchHit Construction Updates - Other Modules
modules/parent-join/src/yamlRestTest/resources/rest-api-spec/test/30_inner_hits.yml, modules/percolator/src/main/java/org/elasticsearch/percolator/*.java (2 files), modules/reindex/src/test/java/org/elasticsearch/reindex/*.java (2 files), modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/SearchTemplateResponseTests.java
Updates SearchHit constructor calls and HitContext instantiations to match new signatures; adds Map parameters to HitContext constructors.
HitContext Constructor Usage Updates
server/src/test/java/org/elasticsearch/search/fetch/subphase/*.java (2 files), test/framework/src/main/java/org/elasticsearch/search/fetch/HighlighterTestCase.java, modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorMatchedSlotSubFetchPhaseTests.java
Updates HitContext instantiations to include new Map parameter before Source parameter.
Highlight and Fetch Profile Integration
server/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/HighlightPhase.java, server/src/main/java/org/elasticsearch/search/fetch/FetchProfiler.java
Integrates StoredFieldsSpec into highlight and profile processing with context tracking and delegation patterns.
Profile Output Documentation
docs/reference/search/profile.asciidoc, rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search/370_profile.yml, modules/parent-join/build.gradle, rest-api-spec/build.gradle
Updates profile examples and test expectations to include new StoredFieldsPhase entry; adds skip rules for profile tests with changed output.
Comprehensive Test Updates - SearchHit Construction
server/src/test/java/org/elasticsearch/action/search/*.java (3 files), server/src/test/java/org/elasticsearch/search/SearchHit*.java (3 files), server/src/test/java/org/elasticsearch/search/aggregations/metrics/InternalTopHitsTests.java, server/src/test/java/org/elasticsearch/index/mapper/Ignored/RoutingFieldTypeTests.java (2 files)
Updates all test code to use 2-argument SearchHit constructor and explicit addDocumentFields() calls for field attachment.
X-Pack Plugin Test Updates
x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/execution/assembler/*.java (3 files), x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/execution/search/CriterionOrdinalExtractionTests.java, x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/*.java (2 files), x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/*.java (2 files), x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/*.java (3 files), x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/**/*Tests.java (6 files), x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/execution/search/extractor/*.java (3 files)
Updates X-Pack test code to align with SearchHit constructor and HitContext signature changes; applies post-construction field attachment pattern.
Supporting Classes and Utilities
server/src/main/java/org/elasticsearch/search/DefaultSearchContext.java, server/src/main/java/org/elasticsearch/search/SearchModule.java, server/src/main/java/org/elasticsearch/search/internal/SubSearchContext.java, server/src/main/java/org/elasticsearch/index/fieldvisitor/CustomFieldsVisitor.java, server/src/main/java/org/elasticsearch/index/get/ShardGetService.java, server/src/main/java/org/elasticsearch/search/fetch/StoredFieldsContext.java
Updates context classes, module registration, and field visitor logic to support new StoredFieldsSpec; tightens hasScriptFields() conditions; makes StoredFieldsContext.fetchFields final.
New Test Coverage
server/src/test/java/org/elasticsearch/search/fetch/StoredFieldsSpecTests.java, x-pack/plugin/async-search/src/test/java/org/elasticsearch/xpack/search/AsyncSearchSingleNodeTests.java, server/src/internalClusterTest/java/org/elasticsearch/action/search/TransportSearchIT.java, server/src/internalClusterTest/java/org/elasticsearch/search/fetch/FetchSubPhasePluginIT.java
Introduces new unit tests for StoredFieldsSpec behavior and adds storedFieldsSpec() implementations in plugin test harnesses.

Sequence Diagram(s)

The changes do not meet the criteria for sequence diagram generation as they primarily involve:

  • Constructor signature simplifications and refactoring (no new multi-component flows)
  • Interface extension with new methods (no new sequential interaction patterns)
  • Widespread pattern application across similar components (homogeneous, not revealing new flows)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 By the whisker's gleam, a fetch refine,
SearchHits now skip the field-map line,
StoredFieldsSpec shows what's required,
Post-construction binding, newly wired,
Cleaner contracts, simpler views,
A hoppy hop through archit-ews!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: allowing FetchSubPhaseProcessors to report their required stored fields, which aligns with the introduction of StoredFieldsSpec and related infrastructure throughout the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
modules/parent-join/src/yamlRestTest/resources/rest-api-spec/test/30_inner_hits.yml (1)

146-154: Duplicate assertions detected in profile breakdown checks.

Lines 148-149 duplicate the assertions from lines 146-147, and lines 153-154 duplicate lines 151-152. This appears to be a copy-paste error that should be cleaned up.

🛠️ Suggested fix
   - match: { profile.shards.0.fetch.children.0.type: FetchSourcePhase }
   - gt: { profile.shards.0.fetch.children.0.breakdown.next_reader_count: 0 }
   - gt: { profile.shards.0.fetch.children.0.breakdown.next_reader: 0 }
-  - gt: { profile.shards.0.fetch.children.0.breakdown.next_reader_count: 0 }
-  - gt: { profile.shards.0.fetch.children.0.breakdown.next_reader: 0 }
   - match: { profile.shards.0.fetch.children.1.type: InnerHitsPhase }
   - gt: { profile.shards.0.fetch.children.1.breakdown.next_reader_count: 0 }
   - gt: { profile.shards.0.fetch.children.1.breakdown.next_reader: 0 }
-  - gt: { profile.shards.0.fetch.children.1.breakdown.next_reader_count: 0 }
-  - gt: { profile.shards.0.fetch.children.1.breakdown.next_reader: 0 }
   - match: { profile.shards.0.fetch.children.2.type: StoredFieldsPhase }
🤖 Fix all issues with AI agents
In `@server/src/main/java/org/elasticsearch/search/fetch/FetchContext.java`:
- Around line 67-73: The loop over sfc.fieldNames() can NPE when fieldNames()
returns null (e.g., metadataOnly()); before iterating, guard with a null check
on sfc.fieldNames() — in the block that checks sfc != null && sfc.fetchFields(),
add a condition or early continue ensuring sfc.fieldNames() != null, then
perform the for-each and update fsc using FetchSourceContext.of(true) when
SourceFieldMapper.NAME matches.

In
`@server/src/main/java/org/elasticsearch/search/fetch/FetchSubPhaseProcessor.java`:
- Around line 40-43: The new abstract method storedFieldsSpec() on
FetchSubPhaseProcessor will break binary compatibility for existing plugins;
change its declaration to a default method on the FetchSubPhaseProcessor
interface that returns StoredFieldsSpec.NO_REQUIREMENTS so existing
implementations continue to work without recompilation, i.e., update the
storedFieldsSpec() signature in FetchSubPhaseProcessor to a default
implementation returning StoredFieldsSpec.NO_REQUIREMENTS.

In `@server/src/main/java/org/elasticsearch/search/SearchHit.java`:
- Around line 433-436: The public method SearchHit.addDocumentFields currently
calls putAll directly and will NPE if callers pass null; update
addDocumentFields to guard against null inputs by checking docFields and
metaFields before calling this.documentFields.putAll(docFields) and
this.metaFields.putAll(metaFields) (treat null as no-op/empty), so the method
tolerates null maps and preserves previous constructor behavior.

In `@server/src/test/java/org/elasticsearch/search/SearchHitTests.java`:
- Around line 396-399: The test creates a metadata Map<String, DocumentField>
containing a "_routing" DocumentField but then calls
SearchHit.addDocumentFields(fields, Collections.emptyMap()), leaving the
metadata unused; update the call to pass the created metadata map so the test
exercises metadata handling (i.e., replace the empty map argument with the
metadata variable) and verify SearchHit serialization/deserialization or
assertions that rely on the "_routing" metadata field (locate the SearchHit
instantiation, the metadata Map declaration, and the addDocumentFields(...)
call).

In
`@x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/persistence/MockClientBuilder.java`:
- Around line 192-193: The call to SearchHit.addDocumentFields has its arguments
reversed: pass the test document fields as the first argument and the empty
metadata map as the second; update the call in MockClientBuilder where SearchHit
hit = new SearchHit(10, null) is created to call addDocumentFields with
fields.get(i) as the first parameter and Map.of() as the second (i.e., correct
the argument order for addDocumentFields(Map<String, DocumentField> docFields,
Map<String, DocumentField> metaFields)).
🧹 Nitpick comments (9)
server/src/main/java/org/elasticsearch/search/DefaultSearchContext.java (1)

405-408: LGTM! Correct semantic tightening of hasScriptFields().

The updated logic correctly returns true only when script fields actually exist, rather than when the context object is merely non-null. This aligns with the lazy initialization in scriptFields() which creates an empty context on first access.

Optional style nit: Consider using the more idiomatic negation:

return scriptFields != null && !scriptFields.fields().isEmpty();

,

server/src/main/java/org/elasticsearch/index/fieldvisitor/CustomFieldsVisitor.java (1)

26-30: Consider using removeAll for a more idiomatic approach.

The defensive copy and metadata removal logic is correct. A minor improvement would be to use removeAll instead of creating a temporary List and iterating:

♻️ Suggested refactor
     public CustomFieldsVisitor(Set<String> fields, boolean loadSource) {
         super(loadSource);
         this.fields = new HashSet<>(fields);
         // metadata fields are already handled by FieldsVisitor, so removing
         // them here means that if the only fields requested are metadata
         // fields then we can shortcut loading
-        List.of("_id", "_routing", "_source").forEach(this.fields::remove);
+        this.fields.removeAll(Set.of("_id", "_routing", "_source"));
     }

This also allows removing the java.util.List import on line 13 if not used elsewhere.

server/src/main/java/org/elasticsearch/search/fetch/StoredFieldsSpec.java (2)

28-33: Add final modifier to static constants.

Public static fields should be declared final to prevent accidental reassignment.

♻️ Suggested fix
-    public static StoredFieldsSpec NO_REQUIREMENTS = new StoredFieldsSpec(false, false, Set.of());
+    public static final StoredFieldsSpec NO_REQUIREMENTS = new StoredFieldsSpec(false, false, Set.of());

     /**
      * Use when the source should be loaded but no other stored fields are required
      */
-    public static StoredFieldsSpec NEEDS_SOURCE = new StoredFieldsSpec(true, false, Set.of());
+    public static final StoredFieldsSpec NEEDS_SOURCE = new StoredFieldsSpec(true, false, Set.of());

14-18: Javadoc missing @param for requiresMetadata.

The record parameter requiresMetadata is not documented.

📝 Suggested fix
 /**
  * Defines which stored fields need to be loaded during a fetch
  * `@param` requiresSource        should source be loaded
+ * `@param` requiresMetadata      should metadata fields be loaded
  * `@param` requiredStoredFields  a set of stored fields to load
  */
server/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/HighlightPhase.java (1)

117-167: Avoid collecting stored field names that are never used.
storedFields is populated but the merged spec always uses Set.of(), so the set is dead work right now. If highlighting shouldn't request stored fields yet, consider dropping the set (and HashSet import); otherwise, wire it into StoredFieldsSpec when ready.

♻️ Possible simplification
-            Set<String> storedFields = new HashSet<>();
             boolean sourceRequired = forceSource;
             for (String fieldName : fieldNamesToHighlight) {
                 MappedFieldType fieldType = context.getSearchExecutionContext().getFieldType(fieldName);
@@
-                if (fieldType.isStored()) {
-                    storedFields.add(fieldType.name());
-                } else {
+                if (fieldType.isStored() == false) {
                     sourceRequired = true;
                 }
server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchFieldsPhase.java (1)

43-47: Consider narrowing stored-fields requirements to avoid unnecessary _source loads.
If FieldFetcher can determine which fields truly need source vs stored/docvalues, deriving a more precise StoredFieldsSpec would reduce IO. The TODO hints at this; consider tracking it as a follow-up.

server/src/main/java/org/elasticsearch/search/fetch/subphase/InnerHitsPhase.java (1)

67-75: Consider early return for minor optimization.

The current implementation evaluates all conditions for every subcontext even after requiresSource becomes true. For most use cases this is fine, but an early return could skip unnecessary iterations.

♻️ Optional: Early return optimization
 private static boolean requiresSource(Collection<? extends SearchContext> subContexts) {
-    boolean requiresSource = false;
     for (SearchContext sc : subContexts) {
-        requiresSource |= sc.sourceRequested();
-        requiresSource |= sc.fetchFieldsContext() != null;
-        requiresSource |= sc.highlight() != null;
+        if (sc.sourceRequested() || sc.fetchFieldsContext() != null || sc.highlight() != null) {
+            return true;
+        }
     }
-    return requiresSource;
+    return false;
 }
server/src/main/java/org/elasticsearch/search/fetch/FetchSubPhase.java (1)

31-38: Consider immutability for the loadedFields map.

The loadedFields map is stored and returned directly without wrapping in an unmodifiable view. If callers can modify the map after construction, this could lead to subtle bugs. Consider wrapping in Collections.unmodifiableMap() or Map.copyOf() if the map should be immutable after construction.

However, if performance is critical and callers are trusted internal code, the current approach is acceptable.

♻️ Optional: wrap in unmodifiable view
 public HitContext(SearchHit hit, LeafReaderContext context, int docId, Map<String, List<Object>> loadedFields, Source source) {
     this.hit = hit;
     this.readerContext = context;
     this.docId = docId;
     this.source = source;
-    this.loadedFields = loadedFields;
+    this.loadedFields = loadedFields.isEmpty() ? Map.of() : Collections.unmodifiableMap(loadedFields);
 }
server/src/test/java/org/elasticsearch/search/fetch/StoredFieldsSpecTests.java (1)

73-78: Note: ScriptField constructed with null script.

The ScriptFieldsContext.ScriptField is created with a null script (Line 76). This works for the current test since ScriptFieldsPhase.getProcessor() likely only checks for the presence of script fields, not their actual scripts. However, this coupling could break if the implementation changes.

Consider adding a comment explaining why null is acceptable here, or use a minimal valid script if easily available.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96c68f7 and 547c832.

📒 Files selected for processing (84)
  • docs/reference/search/profile.asciidoc
  • modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/SearchTemplateResponseTests.java
  • modules/parent-join/build.gradle
  • modules/parent-join/src/yamlRestTest/resources/rest-api-spec/test/30_inner_hits.yml
  • modules/percolator/src/main/java/org/elasticsearch/percolator/PercolatorHighlightSubFetchPhase.java
  • modules/percolator/src/main/java/org/elasticsearch/percolator/PercolatorMatchedSlotSubFetchPhase.java
  • modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorMatchedSlotSubFetchPhaseTests.java
  • modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/DiscountedCumulativeGainTests.java
  • modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/ExpectedReciprocalRankTests.java
  • modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/MeanReciprocalRankTests.java
  • modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/PrecisionAtKTests.java
  • modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/RankEvalResponseTests.java
  • modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/RatedSearchHitTests.java
  • modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/RecallAtKTests.java
  • modules/reindex/src/test/java/org/elasticsearch/reindex/AsyncBulkByScrollActionTests.java
  • modules/reindex/src/test/java/org/elasticsearch/reindex/ClientScrollableHitSourceTests.java
  • rest-api-spec/build.gradle
  • rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search/370_profile.yml
  • server/src/internalClusterTest/java/org/elasticsearch/action/search/TransportSearchIT.java
  • server/src/internalClusterTest/java/org/elasticsearch/search/fetch/FetchSubPhasePluginIT.java
  • server/src/main/java/org/elasticsearch/index/fieldvisitor/CustomFieldsVisitor.java
  • server/src/main/java/org/elasticsearch/index/get/ShardGetService.java
  • server/src/main/java/org/elasticsearch/index/mapper/IgnoredFieldMapper.java
  • server/src/main/java/org/elasticsearch/index/mapper/LegacyTypeFieldMapper.java
  • server/src/main/java/org/elasticsearch/index/mapper/RoutingFieldMapper.java
  • server/src/main/java/org/elasticsearch/search/DefaultSearchContext.java
  • server/src/main/java/org/elasticsearch/search/SearchHit.java
  • server/src/main/java/org/elasticsearch/search/SearchModule.java
  • server/src/main/java/org/elasticsearch/search/fetch/FetchContext.java
  • server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java
  • server/src/main/java/org/elasticsearch/search/fetch/FetchProfiler.java
  • server/src/main/java/org/elasticsearch/search/fetch/FetchSubPhase.java
  • server/src/main/java/org/elasticsearch/search/fetch/FetchSubPhaseProcessor.java
  • server/src/main/java/org/elasticsearch/search/fetch/StoredFieldsContext.java
  • server/src/main/java/org/elasticsearch/search/fetch/StoredFieldsSpec.java
  • server/src/main/java/org/elasticsearch/search/fetch/subphase/ExplainPhase.java
  • server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchDocValuesPhase.java
  • server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchFieldsPhase.java
  • server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchScorePhase.java
  • server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchSourcePhase.java
  • server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchVersionPhase.java
  • server/src/main/java/org/elasticsearch/search/fetch/subphase/InnerHitsPhase.java
  • server/src/main/java/org/elasticsearch/search/fetch/subphase/MatchedQueriesPhase.java
  • server/src/main/java/org/elasticsearch/search/fetch/subphase/ScriptFieldsContext.java
  • server/src/main/java/org/elasticsearch/search/fetch/subphase/ScriptFieldsPhase.java
  • server/src/main/java/org/elasticsearch/search/fetch/subphase/SeqNoPrimaryTermPhase.java
  • server/src/main/java/org/elasticsearch/search/fetch/subphase/StoredFieldsPhase.java
  • server/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/HighlightPhase.java
  • server/src/main/java/org/elasticsearch/search/internal/SubSearchContext.java
  • server/src/test/java/org/elasticsearch/action/search/ExpandSearchPhaseTests.java
  • server/src/test/java/org/elasticsearch/action/search/SearchPhaseControllerTests.java
  • server/src/test/java/org/elasticsearch/action/search/SearchResponseTests.java
  • server/src/test/java/org/elasticsearch/index/mapper/IgnoredFieldTypeTests.java
  • server/src/test/java/org/elasticsearch/index/mapper/RoutingFieldTypeTests.java
  • server/src/test/java/org/elasticsearch/search/SearchHitTests.java
  • server/src/test/java/org/elasticsearch/search/SearchHitsTests.java
  • server/src/test/java/org/elasticsearch/search/aggregations/metrics/InternalTopHitsTests.java
  • server/src/test/java/org/elasticsearch/search/fetch/StoredFieldsSpecTests.java
  • server/src/test/java/org/elasticsearch/search/fetch/subphase/FetchFieldsPhaseTests.java
  • server/src/test/java/org/elasticsearch/search/fetch/subphase/FetchSourcePhaseTests.java
  • test/framework/src/main/java/org/elasticsearch/search/fetch/HighlighterTestCase.java
  • x-pack/plugin/async-search/src/test/java/org/elasticsearch/xpack/search/AsyncSearchSingleNodeTests.java
  • x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/action/EnrichShardMultiSearchAction.java
  • x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/execution/assembler/ImplicitTiebreakerTests.java
  • x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/execution/assembler/SequenceSpecTests.java
  • x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/execution/search/CriterionOrdinalExtractionTests.java
  • x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/execution/sequence/CircuitBreakerTests.java
  • x-pack/plugin/logstash/src/test/java/org/elasticsearch/xpack/logstash/action/TransportGetPipelineActionTests.java
  • x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TransportDeleteForecastActionTests.java
  • x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/datafeed/extractor/scroll/ScrollDataExtractorTests.java
  • x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/dataframe/process/DataFrameRowsJoinerTests.java
  • x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/persistence/JobResultsProviderTests.java
  • x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/persistence/MockClientBuilder.java
  • x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/test/SearchHitBuilder.java
  • x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java
  • x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java
  • x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/IndexServiceAccountTokenStoreTests.java
  • x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java
  • x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/execution/search/extractor/ComputingExtractorTests.java
  • x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/execution/search/extractor/FieldHitExtractorTests.java
  • x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/execution/search/extractor/TopHitsAggExtractorTests.java
  • x-pack/plugin/watcher/src/internalClusterTest/java/org/elasticsearch/xpack/watcher/condition/CompareConditionSearchTests.java
  • x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherServiceTests.java
  • x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/execution/TriggeredWatchStoreTests.java
🧰 Additional context used
🧬 Code graph analysis (9)
server/src/main/java/org/elasticsearch/search/fetch/FetchContext.java (3)
server/src/main/java/org/elasticsearch/index/mapper/SourceFieldMapper.java (1)
  • SourceFieldMapper (34-273)
server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchSourceContext.java (1)
  • FetchSourceContext (35-264)
server/src/main/java/org/elasticsearch/search/fetch/StoredFieldsContext.java (1)
  • StoredFieldsContext (30-196)
server/src/main/java/org/elasticsearch/search/fetch/subphase/InnerHitsPhase.java (1)
server/src/main/java/org/elasticsearch/search/internal/SearchContext.java (1)
  • SearchContext (59-377)
server/src/main/java/org/elasticsearch/search/fetch/subphase/StoredFieldsPhase.java (7)
server/src/main/java/org/elasticsearch/index/mapper/IgnoredFieldMapper.java (1)
  • IgnoredFieldMapper (23-91)
server/src/main/java/org/elasticsearch/index/mapper/LegacyTypeFieldMapper.java (1)
  • LegacyTypeFieldMapper (26-109)
server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java (1)
  • MappedFieldType (58-606)
server/src/main/java/org/elasticsearch/index/mapper/RoutingFieldMapper.java (1)
  • RoutingFieldMapper (21-134)
server/src/main/java/org/elasticsearch/index/mapper/SourceFieldMapper.java (1)
  • SourceFieldMapper (34-273)
server/src/main/java/org/elasticsearch/search/fetch/FetchContext.java (1)
  • FetchContext (37-278)
server/src/main/java/org/elasticsearch/search/fetch/StoredFieldsContext.java (1)
  • StoredFieldsContext (30-196)
server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java (1)
server/src/main/java/org/elasticsearch/index/fieldvisitor/StoredFieldLoader.java (1)
  • StoredFieldLoader (27-173)
server/src/main/java/org/elasticsearch/search/SearchModule.java (1)
server/src/main/java/org/elasticsearch/search/fetch/subphase/StoredFieldsPhase.java (1)
  • StoredFieldsPhase (36-122)
server/src/test/java/org/elasticsearch/search/fetch/StoredFieldsSpecTests.java (5)
server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java (1)
  • SearchSourceBuilder (73-1853)
server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchSourcePhase.java (1)
  • FetchSourcePhase (22-97)
server/src/main/java/org/elasticsearch/search/fetch/subphase/ScriptFieldsPhase.java (1)
  • ScriptFieldsPhase (26-95)
server/src/main/java/org/elasticsearch/search/fetch/subphase/StoredFieldsPhase.java (1)
  • StoredFieldsPhase (36-122)
server/src/main/java/org/elasticsearch/search/internal/SearchContext.java (1)
  • SearchContext (59-377)
server/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/HighlightPhase.java (1)
server/src/main/java/org/elasticsearch/search/fetch/subphase/FieldFetcher.java (1)
  • FieldContext (294-302)
server/src/test/java/org/elasticsearch/index/mapper/IgnoredFieldTypeTests.java (1)
server/src/main/java/org/elasticsearch/index/mapper/IgnoredFieldMapper.java (1)
  • IgnoredFieldMapper (23-91)
server/src/test/java/org/elasticsearch/index/mapper/RoutingFieldTypeTests.java (1)
server/src/main/java/org/elasticsearch/index/mapper/RoutingFieldMapper.java (1)
  • RoutingFieldMapper (21-134)
🔇 Additional comments (117)
x-pack/plugin/watcher/src/internalClusterTest/java/org/elasticsearch/xpack/watcher/condition/CompareConditionSearchTests.java (1)

89-91: Constructor update matches new SearchHit API.
This is consistent with the refactor and keeps the test focused on score/shard fields.

modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/SearchTemplateResponseTests.java (1)

160-160: Constructor update looks correct.

The two-argument SearchHit constructor is sufficient for this test since only _id and _score are asserted.

x-pack/plugin/logstash/src/test/java/org/elasticsearch/xpack/logstash/action/TransportGetPipelineActionTests.java (1)

213-223: Looks good with the new SearchHit constructor.

The updated instantiation fits the new API shape and the test only relies on id/score/shard.

x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/execution/sequence/CircuitBreakerTests.java (4)

109-113: LGTM — constructor update is consistent.


125-126: LGTM — new constructor fits test usage.


389-393: LGTM — constructor update aligns with new SearchHit API.


436-440: LGTM — constructor update aligns with new SearchHit API.

x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/test/SearchHitBuilder.java (2)

24-25: Constructor update aligns with new SearchHit API.


32-34: Field attachment via setDocumentField looks correct.

x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/execution/assembler/ImplicitTiebreakerTests.java (2)

76-82: LGTM!

The constructor simplification from four arguments to two arguments is correct. The sortValues are properly attached post-construction, which aligns with the new pattern of using the simpler constructor and adding fields via dedicated API calls.


94-96: LGTM!

The simplified two-argument SearchHit constructor is correctly used here. Since this is a fetch operation that only needs the hit reference ID, no additional fields are required.

test/framework/src/main/java/org/elasticsearch/search/fetch/HighlighterTestCase.java (1)

65-70: LGTM — updated HitContext construction matches the new API.

The test adapts cleanly to the new SearchHit constructor and loaded-fields map parameter.

modules/reindex/src/test/java/org/elasticsearch/reindex/ClientScrollableHitSourceTests.java (1)

162-162: LGTM!

The simplified SearchHit constructor usage aligns with the PR's refactoring to use the two-argument constructor. The chained sourceRef() call properly sets the source content needed for the test assertions in assertSameHits.

x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/persistence/JobResultsProviderTests.java (1)

924-926: LGTM!

The migration to the new SearchHit construction pattern is correctly implemented. The helper method now:

  1. Creates a SearchHit with the simplified two-argument constructor
  2. Attaches document fields via addDocumentFields
  3. Sets the source via sourceRef

This maintains functional equivalence with the previous approach while aligning with the broader refactoring in this PR.

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/execution/search/extractor/ComputingExtractorTests.java (1)

86-88: LGTM!

The test correctly adapts to the new SearchHit constructor pattern by using the minimal constructor and then attaching the field via setDocumentField. The test logic is preserved and aligns with the PR's goal of simplifying SearchHit construction.

x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/execution/assembler/SequenceSpecTests.java (2)

191-193: LGTM!

The migration to the 2-argument SearchHit constructor with post-construction field attachment via addDocumentFields(documentFields, Map.of()) is correct and aligns with the updated API pattern.


234-234: LGTM!

Correct migration to the 2-argument SearchHit constructor. No addDocumentFields call is needed here since this test only uses the hit reference id for lookup purposes.

server/src/test/java/org/elasticsearch/search/aggregations/metrics/InternalTopHitsTests.java (1)

155-159: LGTM!

The test correctly adapts to the new SearchHit API pattern. The two-step construction (creating with docId and id, then attaching fields via addDocumentFields) aligns with the PR's refactoring to simplify the SearchHit constructor. The empty searchHitFields map is appropriate here since this test focuses on TopHits aggregation scoring and sorting behavior rather than document field handling.

x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/execution/TriggeredWatchStoreTests.java (2)

228-231: LGTM!

The SearchHit constructor update correctly uses the new simplified two-argument signature, removing the trailing null maps. The subsequent setup for version, shard target, and source reference remains unchanged, preserving the test's intent.


243-246: LGTM!

Consistent with the first change—correctly adapts to the simplified SearchHit constructor while maintaining the same test setup pattern for the second search hit used in scroll pagination testing.

server/src/main/java/org/elasticsearch/index/fieldvisitor/CustomFieldsVisitor.java (1)

17-18: LGTM!

The updated class documentation accurately reflects that _id and _routing are always loaded by the parent FieldsVisitor, clarifying the behavior for future maintainers.

server/src/main/java/org/elasticsearch/index/mapper/LegacyTypeFieldMapper.java (2)

32-41: Centralized FIELD_TYPE usage looks good.
The shared constant and constructor update align with the refactor and reduce redundant instances.


49-49: Encapsulating the field type class is a nice cleanup.
Limiting visibility here keeps the API surface tighter without affecting behavior.

server/src/main/java/org/elasticsearch/index/mapper/IgnoredFieldMapper.java (1)

43-76: FIELD_TYPE exposure and reuse look consistent.
This aligns with the refactor pattern and keeps construction clean.

server/src/main/java/org/elasticsearch/index/mapper/RoutingFieldMapper.java (1)

70-104: Routing FIELD_TYPE constant and constructor change look good.
Consistent with the new public access pattern and avoids inline instantiation.

server/src/test/java/org/elasticsearch/index/mapper/RoutingFieldTypeTests.java (3)

21-21: Test update matches the new FIELD_TYPE API.


38-38: Test update matches the new FIELD_TYPE API.


51-51: Test update matches the new FIELD_TYPE API.

server/src/test/java/org/elasticsearch/index/mapper/IgnoredFieldTypeTests.java (3)

22-22: Test update matches the new FIELD_TYPE API.


39-39: Test update matches the new FIELD_TYPE API.


52-52: Test update matches the new FIELD_TYPE API.

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/execution/search/extractor/TopHitsAggExtractorTests.java (1)

119-127: LGTM — new SearchHit construction is clear and consistent.

server/src/main/java/org/elasticsearch/search/internal/SubSearchContext.java (1)

92-94: LGTM! More accurate semantic check for script fields presence.

This change correctly distinguishes between having a ScriptFieldsContext container and actually having script fields defined. The scriptFields.fields() method always returns a non-null ArrayList, so no NPE risk.

Minor style nit: !scriptFields.fields().isEmpty() is slightly more idiomatic than scriptFields.fields().isEmpty() == false, but both are correct.

server/src/main/java/org/elasticsearch/index/get/ShardGetService.java (2)

24-24: Import addition looks fine.

Line 24 adds MappedFieldType to support the new conversion logic.


270-274: Confirm behavior for stored fields with no current mapping.

On Line 270–Line 274, fields are skipped when mapperService.fieldType(...) returns null. If stored fields exist but mappings were removed/changed, this now omits requested fields instead of returning raw stored values. Please confirm this is intended (and covered by tests). If not, consider falling back to raw values when ft is null.

Optional fallback if you want to preserve raw stored values
-                MappedFieldType ft = mapperService.fieldType(entry.getKey());
-                if (ft == null) {
-                    continue;   // user asked for a non-existent field, ignore it
-                }
-                List<Object> values = entry.getValue().stream().map(ft::valueForDisplay).toList();
+                MappedFieldType ft = mapperService.fieldType(entry.getKey());
+                List<Object> values = entry.getValue();
+                if (ft != null) {
+                    values = values.stream().map(ft::valueForDisplay).toList();
+                }
server/src/main/java/org/elasticsearch/search/SearchModule.java (1)

219-219: LGTM!

The StoredFieldsPhase import and registration follow the established pattern for fetch sub-phases. The placement in the registration order (after ExplainPhase) is consistent with the new stored fields handling architecture introduced in this PR.

Also applies to: 993-993

x-pack/plugin/async-search/src/test/java/org/elasticsearch/xpack/search/AsyncSearchSingleNodeTests.java (1)

126-129: LGTM!

The storedFieldsSpec() override correctly returns StoredFieldsSpec.NO_REQUIREMENTS, which is appropriate for this test stub since the processor only checks the hit ID and doesn't require any stored fields to be loaded.

x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TransportDeleteForecastActionTests.java (1)

88-90: LGTM!

The refactored SearchHit construction correctly uses the simplified two-argument constructor followed by addDocumentFields() to attach the forecast status fields. This aligns with the PR's API simplification pattern.

x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/action/EnrichShardMultiSearchAction.java (1)

272-274: LGTM!

The simplified SearchHit constructor is appropriate here since the enrich action only needs to set the source via sourceRef() and doesn't require document fields or metadata fields. The previous empty map arguments were unnecessary.

x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherServiceTests.java (1)

211-214: LGTM!

The simplified SearchHit constructor is correctly used here. The test only requires the doc ID and hit ID for creating mock hits that are subsequently processed by the watch parser, which operates on source content rather than document fields.

modules/parent-join/build.gradle (1)

23-25: LGTM!

The test skip is necessary for V7 compatibility testing since the introduction of StoredFieldsPhase changes the profile output structure. The skip reason clearly documents why this test is excluded.

rest-api-spec/build.gradle (1)

78-80: Skip entries align with profile output updates.
Rationale is clear and scoped to compatibility transforms.

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java (1)

303-304: Updated SearchHit construction matches new API.
Cleaner instantiation without null placeholders.

server/src/main/java/org/elasticsearch/search/fetch/subphase/ScriptFieldsContext.java (1)

42-42: Good immutability tightening for fields.
Prevents reassignment while preserving behavior.

docs/reference/search/profile.asciidoc (1)

196-206: Docs now reflect StoredFieldsPhase profiling.
Examples stay consistent with updated fetch profiling output.

Also applies to: 1025-1035

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search/370_profile.yml (3)

25-47: Profile test updated for StoredFieldsPhase in fetch fields.
Skip range and child expectations match new profile output.


56-82: Profile test updated for StoredFieldsPhase in fetch source.
Child count and phase type assertions are consistent.


86-150: Profile test updated for StoredFieldsPhase in nested fetch.
Additional child expectations reflect new phase ordering.

modules/percolator/src/main/java/org/elasticsearch/percolator/PercolatorMatchedSlotSubFetchPhase.java (1)

29-30: Explicit stored-fields requirements are clear.
NO_REQUIREMENTS is appropriate for this sub-phase.

Also applies to: 73-76

modules/reindex/src/test/java/org/elasticsearch/reindex/AsyncBulkByScrollActionTests.java (1)

568-568: LGTM!

The SearchHit construction correctly migrates to the new 2-argument form with source attachment via sourceRef(), consistent with the PR's API refactoring pattern.

x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/datafeed/extractor/scroll/ScrollDataExtractorTests.java (1)

543-544: LGTM!

The migration to the 2-argument SearchHit constructor with addDocumentFields() for field attachment is correct and maintains the test's functionality.

server/src/main/java/org/elasticsearch/search/fetch/StoredFieldsSpec.java (1)

38-46: LGTM!

The merge() method correctly creates a new HashSet to avoid mutating the original sets and properly combines the specifications using logical OR for boolean flags and set union for required fields.

server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchScorePhase.java (1)

45-48: LGTM!

The storedFieldsSpec() override correctly returns NO_REQUIREMENTS since score computation uses Lucene's Scorer and doesn't require any stored fields to be loaded.

server/src/main/java/org/elasticsearch/search/fetch/subphase/ScriptFieldsPhase.java (1)

42-49: LGTM!

The storedFieldsSpec() correctly declares that script fields require metadata loading (for access to fields like _id, _routing) while deferring source loading to SearchLookup's lazy mechanism. The inline comment provides helpful context for this design decision.

server/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/HighlightPhase.java (1)

19-27: StoredFieldsSpec plumbing is clear and well-contained.
Returning the precomputed spec from the processor keeps fetch orchestration aligned with highlighting needs, and reusing the prebuilt builders avoids per-hit recomputation.

Also applies to: 49-66

server/src/main/java/org/elasticsearch/search/SearchHit.java (1)

88-152: Non-null field maps and simplified constructors look good.
Eager map initialization plus the streamlined constructors (and createFromMap using addDocumentFields) keep field handling consistent and avoid null checks.

Also applies to: 843-845

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java (1)

905-911: Test hit construction aligned with the new SearchHit API.
Using the (docId, id) constructor keeps this test consistent with the updated usage pattern.

x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/dataframe/process/DataFrameRowsJoinerTests.java (1)

311-314: SearchHit construction update looks good.
Switching to the two-arg constructor and setting the source explicitly matches the new API pattern.

x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/execution/search/CriterionOrdinalExtractionTests.java (1)

150-156: Field attachment via addDocumentFields is consistent.
The updated construction keeps the test data aligned with the new SearchHit API while preserving field values.

modules/percolator/src/main/java/org/elasticsearch/percolator/PercolatorHighlightSubFetchPhase.java (2)

21-66: StoredFieldsSpec override is appropriate here.
Explicitly returning NO_REQUIREMENTS clarifies that percolator highlighting doesn’t rely on stored fields from the main fetch phase.


92-96: HitContext construction updated cleanly.
Using the new SearchHit constructor and supplying the loaded-fields map keeps this sub-context aligned with the new signature.

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java (1)

1016-1016: LGTM: constructor update aligns with the new SearchHit API.

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/execution/search/extractor/FieldHitExtractorTests.java (2)

96-99: LGTM: field attachment after SearchHit construction is consistent with the new pattern.

Also applies to: 115-117, 130-132, 147-149, 166-168, 177-179, 190-192, 207-209, 214-216, 226-228, 240-242


157-158: No change needed. The 3-arg SearchHit constructor still exists and the code at line 157 compiles as-is. The constructor signature SearchHit(int nestedTopDocId, String id, NestedIdentity nestedIdentity) is available in the current codebase.

Likely an incorrect or invalid review comment.

server/src/internalClusterTest/java/org/elasticsearch/action/search/TransportSearchIT.java (1)

96-99: LGTM: explicit StoredFieldsSpec declaration is clear and minimal.

server/src/test/java/org/elasticsearch/action/search/SearchPhaseControllerTests.java (1)

425-436: LGTM: simplified SearchHit construction matches updated API.

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/IndexServiceAccountTokenStoreTests.java (1)

266-269: LGTM: SearchHit construction updated cleanly.

modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/DiscountedCumulativeGainTests.java (1)

63-64: LGTM: constructor updates are consistent with the new SearchHit API.

Also applies to: 113-114, 170-171

server/src/main/java/org/elasticsearch/search/fetch/subphase/MatchedQueriesPhase.java (1)

76-79: Stored fields requirement declaration looks good.
Returning NO_REQUIREMENTS matches this sub-phase’s behavior.

server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchDocValuesPhase.java (1)

63-66: Stored fields requirement declaration looks good.
NO_REQUIREMENTS aligns with doc values fetching.

server/src/main/java/org/elasticsearch/search/fetch/subphase/ExplainPhase.java (1)

47-50: Stored fields requirement declaration looks good.
NO_REQUIREMENTS is appropriate for explain.

server/src/internalClusterTest/java/org/elasticsearch/search/fetch/FetchSubPhasePluginIT.java (1)

120-123: Stored fields requirement declaration looks good.
This test sub-phase correctly declares no stored fields.

server/src/test/java/org/elasticsearch/search/fetch/subphase/FetchSourcePhaseTests.java (1)

173-179: Test updates align with the new constructors.
The updated SearchHit and HitContext instantiations look correct.

modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/RatedSearchHitTests.java (2)

29-29: Constructor update looks good.
This aligns with the simplified SearchHit API.


39-39: Constructor update looks good.
Mutation path uses the new SearchHit signature consistently.

server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchVersionPhase.java (1)

17-39: Good alignment with stored-fields contract.
Returning StoredFieldsSpec.NO_REQUIREMENTS here is consistent with the phase’s needs and keeps behavior unchanged.

modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/MeanReciprocalRankTests.java (1)

190-194: Test update matches new SearchHit constructor.
This keeps the test aligned with the new SearchHit API.

server/src/test/java/org/elasticsearch/action/search/SearchResponseTests.java (1)

217-219: SearchHit construction updated appropriately.
Using the two-arg constructor keeps the test in sync with the new API.

modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/RecallAtKTests.java (1)

101-107: Constructor changes look good.
The updated SearchHit instantiation matches the new API and keeps the tests consistent.

Also applies to: 216-221

modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorMatchedSlotSubFetchPhaseTests.java (1)

36-38: HitContext updates are consistent with new signature.
Passing Map.of() matches the constructor change and keeps the test setup clean.

Also applies to: 59-60, 90-91, 120-121

server/src/test/java/org/elasticsearch/search/fetch/subphase/FetchFieldsPhaseTests.java (1)

32-36: Test wiring matches updated HitContext signature.
The added empty map is appropriate for this test case.

Also applies to: 84-87

modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/RankEvalResponseTests.java (1)

199-203: LGTM: constructor update matches the new SearchHit API.

modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/ExpectedReciprocalRankTests.java (1)

100-108: LGTM: aligned SearchHit construction in tests.

server/src/main/java/org/elasticsearch/search/fetch/FetchProfiler.java (1)

125-128: LGTM: profiling wrapper now exposes stored-fields requirements.

server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchFieldsPhase.java (1)

11-18: No review needed for import-only change.

server/src/main/java/org/elasticsearch/search/fetch/subphase/SeqNoPrimaryTermPhase.java (2)

14-18: No review needed for import-only change.


38-41: LGTM: stored-fields requirement accurately declared as none.

server/src/main/java/org/elasticsearch/search/fetch/subphase/InnerHitsPhase.java (1)

46-56: LGTM! Clean implementation of storedFieldsSpec() override.

The StoredFieldsSpec is correctly computed once during processor creation and captured in the closure for efficient reuse. The implementation properly declares source requirements based on the inner hits' configurations.

server/src/test/java/org/elasticsearch/search/SearchHitsTests.java (2)

229-229: LGTM! Test updated to use simplified SearchHit constructor.

The test correctly uses the new two-argument constructor, aligning with the broader API simplification in this PR.


253-253: LGTM! Consistent with the SearchHit constructor simplification.

server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchSourcePhase.java (2)

40-43: LGTM! Correct storedFieldsSpec() implementation.

Returning StoredFieldsSpec.NEEDS_SOURCE is the appropriate choice for FetchSourcePhase since it needs to load the document source.


47-47: LGTM! Index name retrieval scoped appropriately.

Moving the index name retrieval into process() keeps it close to its usage in the error message and avoids capturing it unnecessarily in the outer scope.

server/src/main/java/org/elasticsearch/search/fetch/StoredFieldsContext.java (2)

34-34: LGTM! Good immutability improvement.

Making fetchFields final enforces immutability for this field, which is a sound practice.


146-148: Confirm that metadataOnly() contexts prevent field additions via null check.

The metadataOnly() factory creates a context with fieldNames = null and fetchFields = true. Calling addFieldName() or addFieldNames() on such a context will throw a NullPointerException at lines 99 and 88 respectively. This design pattern successfully prevents mutation of metadata-only contexts, though the safety mechanism is implicit rather than explicit (the null reference silently rejects modifications). The single usage in the codebase (FetchContext.java:84) confirms this is the intended pattern—contexts created by metadataOnly() are used directly without further field additions.

server/src/main/java/org/elasticsearch/search/fetch/FetchContext.java (4)

50-52: LGTM! Clean initialization of context fields.

The constructor properly delegates to builder methods for both stored fields and fetch source contexts.


80-87: LGTM! Clean default handling for stored fields context.

The method correctly defaults to metadataOnly() when no stored fields context is provided.


107-107: LGTM! Simplified lookup retrieval.

Getting the lookup directly from SearchExecutionContext instead of storing it as a field is cleaner and avoids potential staleness issues.


141-150: LGTM! Clean accessors for the new context fields.

The accessors return the pre-computed contexts, consistent with the rest of the class design.

server/src/test/java/org/elasticsearch/action/search/ExpandSearchPhaseTests.java (5)

47-47: LGTM! Consistent use of simplified SearchHit constructor.


111-113: LGTM! Proper pattern for constructing SearchHit with document fields.

The test correctly uses the two-argument constructor followed by setDocumentField() to attach the collapse field, aligning with the PR's refactored approach.


140-140: LGTM!


178-182: LGTM! Consistent pattern applied to multiple hits.


206-210: LGTM! Test correctly updated for the new SearchHit construction pattern.

server/src/main/java/org/elasticsearch/search/fetch/FetchSubPhase.java (1)

71-73: LGTM!

The accessor method for loadedFields is appropriately implemented, providing access to the stored fields map for fetch sub-phases to process.

modules/rank-eval/src/test/java/org/elasticsearch/index/rankeval/PrecisionAtKTests.java (2)

103-104: LGTM!

The update to use the simplified two-argument SearchHit constructor is consistent with the API refactoring. Since this test only needs basic hit identification for rank evaluation, the new constructor is appropriate.


248-255: LGTM!

The toSearchHits helper method is correctly updated to use the new SearchHit(docId, id) constructor. The test logic remains unchanged and properly validates precision-at-k calculations.

server/src/main/java/org/elasticsearch/search/fetch/subphase/StoredFieldsPhase.java (3)

91-119: LGTM on the processor implementation.

The anonymous FetchSubPhaseProcessor implementation is well-structured:

  • Empty setNextReader is appropriate since no per-segment state is needed
  • The process method correctly categorizes fields into document fields and metadata fields
  • storedFieldsSpec() properly returns the pre-computed spec

39-54: LGTM on the StoredField record.

The inner record cleanly encapsulates field metadata and processing logic. Using ft::valueForDisplay to transform stored values is the correct approach for consistent value representation.


70-89: I need the review comment to rewrite. Please provide it within <review_comment> tags along with any relevant repository context or file paths for verification.

server/src/test/java/org/elasticsearch/search/fetch/StoredFieldsSpecTests.java (2)

26-46: LGTM on testDefaults.

The test correctly validates:

  • Default source loading behavior (requiresSource=true)
  • Default stored fields behavior (requiresSource=false)
  • Merge semantics preserve source requirement

48-55: LGTM on testStoredFieldsDisabled.

Good test coverage verifying that "_none_" disables both stored fields and source fetching, returning null processors.

server/src/test/java/org/elasticsearch/search/SearchHitTests.java (2)

74-75: LGTM!

The test correctly adapts to the new SearchHit constructor pattern, constructing the hit first and then attaching fields via addDocumentFields.


228-246: LGTM!

The test correctly creates nested inner hits using the simplified constructor, maintaining proper shard target propagation verification.

server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java (4)

97-106: LGTM on StoredFieldsSpec aggregation.

The logic correctly:

  1. Starts with NO_REQUIREMENTS as the base
  2. Merges each processor's spec
  3. Includes the source loader's required stored fields
  4. Derives the requiresSource flag from the final merged spec

This centralized approach ensures consistent stored field loading across all fetch sub-phases.


236-256: LGTM on non-nested hit context preparation.

The logic correctly handles:

  • Null ID case: passes empty Map.of() for stored fields
  • Normal case: passes leafStoredFieldLoader.storedFields() to HitContext
  • Source loading is conditional on requiresSource flag, improving performance when source isn't needed

350-358: LGTM on nested hit context preparation.

The nested hit handling correctly:

  • Passes childFieldLoader.storedFields() to HitContext
  • Returns appropriate Source (from nested source map or empty) based on whether source was loaded

161-166: No issues found. The noRequirements() method is properly defined on the StoredFieldsSpec record at server/src/main/java/org/elasticsearch/search/fetch/StoredFieldsSpec.java (lines 21-23) and correctly called in the code.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +67 to +73
if (sfc != null && sfc.fetchFields()) {
for (String field : sfc.fieldNames()) {
if (SourceFieldMapper.NAME.equals(field)) {
fsc = fsc == null ? FetchSourceContext.of(true) : FetchSourceContext.of(true, fsc.includes(), fsc.excludes());
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential NullPointerException when iterating over fieldNames.

When storedFieldsContext is created via metadataOnly(), fieldNames() returns null. The for-each loop at line 68 will throw an NPE in this case.

🐛 Proposed fix: Add null check before iteration
-        if (sfc != null && sfc.fetchFields()) {
+        if (sfc != null && sfc.fetchFields() && sfc.fieldNames() != null) {
             for (String field : sfc.fieldNames()) {
                 if (SourceFieldMapper.NAME.equals(field)) {
                     fsc = fsc == null ? FetchSourceContext.of(true) : FetchSourceContext.of(true, fsc.includes(), fsc.excludes());
                 }
             }
         }
🤖 Prompt for AI Agents
In `@server/src/main/java/org/elasticsearch/search/fetch/FetchContext.java` around
lines 67 - 73, The loop over sfc.fieldNames() can NPE when fieldNames() returns
null (e.g., metadataOnly()); before iterating, guard with a null check on
sfc.fieldNames() — in the block that checks sfc != null && sfc.fetchFields(),
add a condition or early continue ensuring sfc.fieldNames() != null, then
perform the for-each and update fsc using FetchSourceContext.of(true) when
SourceFieldMapper.NAME matches.

Comment on lines +40 to +43
/**
* The stored fields or source required by this sub phase
*/
StoredFieldsSpec storedFieldsSpec();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential binary compatibility break for plugin sub-phases.
Adding a new abstract method on this SPI will break existing plugins at runtime unless they are recompiled. If backward compatibility is desired, make it a default method that returns StoredFieldsSpec.NO_REQUIREMENTS.

🛠️ Suggested compatibility-preserving default
-    StoredFieldsSpec storedFieldsSpec();
+    default StoredFieldsSpec storedFieldsSpec() {
+        return StoredFieldsSpec.NO_REQUIREMENTS;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* The stored fields or source required by this sub phase
*/
StoredFieldsSpec storedFieldsSpec();
/**
* The stored fields or source required by this sub phase
*/
default StoredFieldsSpec storedFieldsSpec() {
return StoredFieldsSpec.NO_REQUIREMENTS;
}
🤖 Prompt for AI Agents
In
`@server/src/main/java/org/elasticsearch/search/fetch/FetchSubPhaseProcessor.java`
around lines 40 - 43, The new abstract method storedFieldsSpec() on
FetchSubPhaseProcessor will break binary compatibility for existing plugins;
change its declaration to a default method on the FetchSubPhaseProcessor
interface that returns StoredFieldsSpec.NO_REQUIREMENTS so existing
implementations continue to work without recompilation, i.e., update the
storedFieldsSpec() signature in FetchSubPhaseProcessor to a default
implementation returning StoredFieldsSpec.NO_REQUIREMENTS.

Comment on lines +433 to +436
public void addDocumentFields(Map<String, DocumentField> docFields, Map<String, DocumentField> metaFields) {
this.documentFields.putAll(docFields);
this.metaFields.putAll(metaFields);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard addDocumentFields against null inputs.
This public method will throw if a caller passes null (previous constructors tolerated null maps). A small guard keeps the API safe.

🛡️ Null-safe merge
 public void addDocumentFields(Map<String, DocumentField> docFields, Map<String, DocumentField> metaFields) {
-    this.documentFields.putAll(docFields);
-    this.metaFields.putAll(metaFields);
+    if (docFields != null) {
+        this.documentFields.putAll(docFields);
+    }
+    if (metaFields != null) {
+        this.metaFields.putAll(metaFields);
+    }
 }
🤖 Prompt for AI Agents
In `@server/src/main/java/org/elasticsearch/search/SearchHit.java` around lines
433 - 436, The public method SearchHit.addDocumentFields currently calls putAll
directly and will NPE if callers pass null; update addDocumentFields to guard
against null inputs by checking docFields and metaFields before calling
this.documentFields.putAll(docFields) and this.metaFields.putAll(metaFields)
(treat null as no-op/empty), so the method tolerates null maps and preserves
previous constructor behavior.

Comment on lines 396 to +399
Map<String, DocumentField> metadata = new HashMap<>();
metadata.put("_routing", new DocumentField("_routing", Collections.emptyList()));
hit = new SearchHit(0, "_id", fields, Collections.emptyMap());
hit = new SearchHit(0, "_id");
hit.addDocumentFields(fields, Collections.emptyMap());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unused metadata map - test logic may be incomplete.

A metadata map is created with a _routing field (Line 397), but Line 399 passes Collections.emptyMap() instead of metadata to addDocumentFields. This appears to be either:

  1. A copy-paste error from the previous test block
  2. An incomplete test that intended to verify metadata field handling

If the intent is to test that empty metadata fields are omitted from serialization, the metadata map should be passed:

🐛 Proposed fix
 Map<String, DocumentField> metadata = new HashMap<>();
 metadata.put("_routing", new DocumentField("_routing", Collections.emptyList()));
 hit = new SearchHit(0, "_id");
-hit.addDocumentFields(fields, Collections.emptyMap());
+hit.addDocumentFields(fields, metadata);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Map<String, DocumentField> metadata = new HashMap<>();
metadata.put("_routing", new DocumentField("_routing", Collections.emptyList()));
hit = new SearchHit(0, "_id", fields, Collections.emptyMap());
hit = new SearchHit(0, "_id");
hit.addDocumentFields(fields, Collections.emptyMap());
Map<String, DocumentField> metadata = new HashMap<>();
metadata.put("_routing", new DocumentField("_routing", Collections.emptyList()));
hit = new SearchHit(0, "_id");
hit.addDocumentFields(fields, metadata);
🤖 Prompt for AI Agents
In `@server/src/test/java/org/elasticsearch/search/SearchHitTests.java` around
lines 396 - 399, The test creates a metadata Map<String, DocumentField>
containing a "_routing" DocumentField but then calls
SearchHit.addDocumentFields(fields, Collections.emptyMap()), leaving the
metadata unused; update the call to pass the created metadata map so the test
exercises metadata handling (i.e., replace the empty map argument with the
metadata variable) and verify SearchHit serialization/deserialization or
assertions that rely on the "_routing" metadata field (locate the SearchHit
instantiation, the metadata Map declaration, and the addDocumentFields(...)
call).

Comment on lines +192 to +193
SearchHit hit = new SearchHit(10, null);
hit.addDocumentFields(Map.of(), fields.get(i));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find SearchHit class and its addDocumentFields method
fd -t f 'SearchHit.java' --type f

Repository: Signal65/elasticsearch-CodeRabbit

Length of output: 222


🏁 Script executed:

# Search for addDocumentFields method in SearchHit.java
rg -n -A 10 'void addDocumentFields' server/src/main/java/org/elasticsearch/search/SearchHit.java

Repository: Signal65/elasticsearch-CodeRabbit

Length of output: 522


Fix the argument order in addDocumentFields call.

The test fields should be passed as the first argument (document fields), not the second (metadata fields). Based on the addDocumentFields(Map<String, DocumentField> docFields, Map<String, DocumentField> metaFields) signature, the correct call should be:

hit.addDocumentFields(fields.get(i), Map.of());
🤖 Prompt for AI Agents
In
`@x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/persistence/MockClientBuilder.java`
around lines 192 - 193, The call to SearchHit.addDocumentFields has its
arguments reversed: pass the test document fields as the first argument and the
empty metadata map as the second; update the call in MockClientBuilder where
SearchHit hit = new SearchHit(10, null) is created to call addDocumentFields
with fields.get(i) as the first parameter and Map.of() as the second (i.e.,
correct the argument order for addDocumentFields(Map<String, DocumentField>
docFields, Map<String, DocumentField> metaFields)).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants